package performance.inner;

import dataset.Dataset;
import scicalc.Calculate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class InnerPerformanceMeasure {
    private Dataset result;
    private Map<Integer, List<Integer>> clusters;

    public InnerPerformanceMeasure(Dataset result) {
        this.result = result;
        this.clusters = new HashMap<>();
        for (int i = 0; i < this.result.label.length; i++) {
            if (this.result.label[i] == -1)
                continue;
            List<Integer> list = clusters.getOrDefault(this.result.label[i], new ArrayList<>());
            list.add(i);
            clusters.put(this.result.label[i], list);
        }
    }

    private double avgC(List<Integer> c) {
        int len = c.size();
        double distanceSum = 0;
        for (int i = 0; i < len; i++)
            for (int j = i; j < len; j++)
                distanceSum += Calculate.sqrt(Calculate.sum(Calculate.pow(Calculate.minus(this.result.dataset[i], this.result.dataset[j]), 2)));
        return distanceSum / len / (len - 1);
    }

    private double diamC(List<Integer> c) {
        int len = c.size();
        double max = 0;
        for (int i = 0; i < len; i++)
            for (int j = i + 1; j < len; j++) {
                double dist = Calculate.sqrt(Calculate.sum(Calculate.pow(Calculate.minus(this.result.dataset[c.get(i)], this.result.dataset[c.get(j)]), 2)));
                max = dist > max ? dist : max;
            }
        return max;
    }

    private double dMin(List<Integer> c1, List<Integer> c2) {
        double min = Double.MAX_VALUE;
        for (Integer i : c1)
            for (Integer j : c2) {
                double distance = Calculate.sqrt(Calculate.sum(Calculate.pow(Calculate.minus(this.result.dataset[i], this.result.dataset[j]), 2)));
                min = min < distance ? min : distance;
            }
        return min;
    }

    private double dCen(List<Integer> c1, List<Integer> c2) {
        double[] mu1 = Calculate.divide(Calculate.sum(Calculate.choose(this.result.dataset, c1), Calculate.AXIS_0), c1.size());
        double[] mu2 = Calculate.divide(Calculate.sum(Calculate.choose(this.result.dataset, c2), Calculate.AXIS_0), c2.size());
        return Calculate.sqrt(Calculate.sum(Calculate.pow(Calculate.minus(mu1, mu2), 2)));
    }

    public double DBI() {
        int k = clusters.size();
        double sum = 0;
        for (int i = 0; i < k; i++) {
            double max = 0;
            for (int j = 0; j < k; j++) {
                if (i == j) continue;
                double d = (avgC(clusters.get(i)) + avgC(clusters.get(j)) / dCen(clusters.get(i), clusters.get(j)));
                max = d > max ? d : max;
            }
            sum += max;
        }
        return sum / k;
    }

    public double DI() {
        int k = clusters.size();
        double maxDiam = 0;
        for (int i = 0; i < k; i++) {
            double diam = diamC(clusters.get(i));
            maxDiam = diam > maxDiam ? diam : maxDiam;
        }
        double min = Double.MAX_VALUE;
        for (int i = 0; i < k; i++)
            for (int j = i + 1; j < k; j++) {
                double formula = dMin(clusters.get(i), clusters.get(j));
                min = formula < min ? formula : min;
            }
        return min / maxDiam;
    }

    public Map<String, Double> measures() {
        long start = System.nanoTime();
        Map<String, Double> map = new HashMap<>();
        map.put("DBI", DBI());
        map.put("DI", DI());
        long nTime = System.nanoTime() - start;
        double time = nTime / 1000 / 1e3;
        map.put("COST_TIME", time);
        return map;
    }
}
